/*-
 * Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <linux/module.h>
#include "linux/device.h"
#include "linux/platform_device.h"
#ifdef USB_GLOBAL_INCLUDE_FILE
#include USB_GLOBAL_INCLUDE_FILE
#include "controller/usb_host/xhci.h"
#include "controller/usb_host/xhcireg.h"
#endif

static int hixhci_remove(struct platform_device *pdev);

int
hi_xhci_take_controller(device_t self)
{
    struct xhci_softc *sc = device_get_softc(device_get_parent(self));
    uint32_t cparams;
    uint32_t eecp;
    uint32_t eec;
    uint16_t to;
    uint8_t bios_sem;

    cparams = XREAD4(sc, capa, XHCI_HCSPARAMS0);

    eec = (uint32_t)-1;
    /* Synchronise with the BIOS if it owns the controller. */
    for (eecp = XHCI_HCS0_XECP(cparams) << 2; eecp != 0 && XHCI_XECP_NEXT(eec);
        eecp += XHCI_XECP_NEXT(eec) << 2) {
        eec = XREAD4(sc, capa, eecp);/*lint !e40*/

        if (XHCI_XECP_ID(eec) != XHCI_ID_USB_LEGACY)
            continue;/*lint !e40*/
        bios_sem = XREAD1(sc, capa, eecp +
            XHCI_XECP_BIOS_SEM);/*lint !e40*/
        if (bios_sem == 0)
            continue;
        device_printf(sc->sc_bus.bdev, "waiting for BIOS "
            "to give up control\n");
        XWRITE1(sc, capa, eecp +
            XHCI_XECP_OS_SEM, 1);
        to = 500;
        while (1) {
            bios_sem = XREAD1(sc, capa, eecp +
            XHCI_XECP_BIOS_SEM);
            if (bios_sem == 0)
                break;

            if (--to == 0) {
                device_printf(sc->sc_bus.bdev,
                "timed out waiting for BIOS\n");
                break;
            }
            usb_pause_mtx(NULL, hz / 100);	/* wait 10ms */
        }
    }
    return (0);
}
static int hixhci_probe(struct platform_device *pdev)
{
    device_t self = NULL;
    struct resource *res = NULL;
    struct xhci_softc *pst_sc = NULL;
    int err = ENXIO;
    uint8_t usedma32 = 0;
    self = usbmalloc(sizeof(*self),
        M_DEVBUF, M_WAITOK | M_ZERO);

    if(self == NULL) {
        dprintf("hi_ehci root device add fail\n");
        return ENOMEM;
    }

    TAILQ_INIT(&self->dev_children);

    pdev->dev.p = self;

    device_set_desc(self, "hiusb-xhci");
    (void)snprintf_s(self->dev_nameunit, sizeof(self->dev_nameunit), sizeof(self->dev_nameunit) - 1, "%s", "XHCI");
    pst_sc = usbmalloc(sizeof(struct xhci_softc),
        M_DEVBUF, M_WAITOK | M_ZERO);

    if (pst_sc == NULL){
        err = ENOMEM;
        goto error;
    }
    self->dev_sc = pst_sc;
    self->dev_softc_alloc = 1;

    res = platform_get_resource(pdev,IORESOURCE_MEM,0);
    if(res == NULL){
        goto error;
    }

    pst_sc->sc_io_res = platform_ioremap_resource(res);
    pst_sc->sc_io_tag = (void *)pst_sc->sc_io_res;
    pst_sc->sc_io_hdl = (uintptr_t)pst_sc->sc_io_res;
    pst_sc->sc_io_size = res->end - res->start;/*lint !e40*/
    err = bus_request_intr(platform_get_irq(pdev, 0), 0, (irq_handler_t)xhci_interrupt, pst_sc);
    if (err) {
        goto error;/*lint !e40*/
    }

    pst_sc->sc_bus.bdev = device_add_child(self, "usbus", -1);
    if (pst_sc->sc_bus.bdev == NULL) {
        device_printf(self, "Could not add USB device\n");
        goto error;
    }
    device_set_ivars(pst_sc->sc_bus.bdev, &pst_sc->sc_bus);

    hiusb3_start_hcd();
    if (xhci_init(pst_sc, self, usedma32)) {
        device_printf(self, "Could not initialize softc\n");
        goto error;
    }

    err = xhci_halt_controller(pst_sc);
    if (err == 0)
        err = xhci_start_controller(pst_sc);

    usb_callout_init_mtx(&pst_sc->sc_callout, &pst_sc->sc_bus.bus_mtx, 0);

    if (err == 0)
        err = device_probe_and_attach(pst_sc->sc_bus.bdev);

    if (err) {
        device_printf(self, "XHCI halt/start/probe failed err=%d\n", err);
        goto error;
    }
    /* initialise some bus fields */
    self->dev_attached = 1;/*lint !e613*/

    return (0);
error:
    device_printf(self, "XHCI halt/start/probe failed err=%d\n", err);
    (void)hixhci_remove(pdev);

    return err;/*lint !e438*/
}


static int hixhci_suspend(struct device *pst_dev)
{
    device_t dev = pst_dev->p;
    struct xhci_softc *sc = (struct xhci_softc *)device_get_softc(dev);
    device_t bdev = NULL;
    if ((sc != NULL) && (sc->sc_bus.bdev != NULL)) {
       bdev = sc->sc_bus.bdev;
       return DEVICE_SUSPEND(bdev);/*lint !e611*/
    }
    return (0);
}
static int hixhci_resume(struct device *pst_dev)
{
    device_t dev = pst_dev->p;
    struct xhci_softc *sc = (struct xhci_softc *)device_get_softc(dev);
    device_t bdev = NULL;
    if ((sc != NULL) && (sc->sc_bus.bdev != NULL)) {
        bdev = sc->sc_bus.bdev;
        return DEVICE_RESUME(bdev);/*lint !e611*/
    }
    return 0;
}
static const struct dev_pm_op hixhci_dev_pm_ops = {
    .suspend = hixhci_suspend,
    .resume = hixhci_resume,
};
static int hixhci_remove(struct platform_device *pdev)
{
    device_t self = pdev->dev.p;
    struct xhci_softc *pst_sc = NULL;
    device_t bdev = NULL;
    if(self == NULL)
        return -1;
    pst_sc = device_get_softc(self);
    if(pst_sc == NULL)
        goto disable;
    if (pst_sc->sc_bus.bdev != NULL) {
        bdev = pst_sc->sc_bus.bdev;
        (void)device_detach(bdev);
        (void)device_delete_child(self, bdev);
    }
    /* during module unload there are lots of children leftover */
    (void)device_delete_children(self);

    usb_callout_drain(&pst_sc->sc_callout);
    (void)xhci_halt_controller(pst_sc);
    (void)bus_release_intr(platform_get_irq(pdev, 0), pst_sc);/*lint !e40*/
    xhci_uninit(pst_sc);
    if(pst_sc != NULL){
        free(pst_sc);
        ((device_t)(pdev->dev.p))->dev_sc = NULL;
    }
disable:
    if (self != NULL) {
        free(self);
       pdev->dev.p = NULL;
    }
    return (0);/*lint !e438*/
}
static struct platform_driver hixhci_driver = {
    .probe      = hixhci_probe,
    .remove     = hixhci_remove,
    .driver     = {
        .name   = "hi_xhci",
        .pm = &hixhci_dev_pm_ops,
    },
};
int hixhci_init(void)
{
    return platform_driver_register(&hixhci_driver);
}

void hixhci_exit(void)
{
    (void)platform_driver_unregister(&hixhci_driver);
}

module_init(hixhci_init);
module_exit(hixhci_exit);/*lint !e19*/

